'''
 ******************************************************************************
 *  Copyright 2010 Vijay Pandurangan.
 *  Parts copyright Jumo International.
 *
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 ******************************************************************************

@author: vijayp
'''
from ting3.common.t3expvar import *

import tornado.web
from tornado import httpserver
import time
import logging

import os
import thread
import inspect
import sys
import functools

from tornado.ioloop import IOLoop, PeriodicCallback


class ServerException(Exception): 
  pass

class CallbackPendingException(ServerException):
  pass

#TODO: this might be slow


class GuardedCallback(object):
  ''' This class prevents multiple pending callbacks from being enqueued
  at any time.  This might be a bit slow...'''
  
  def __init__(self, fcn, class_name=None, ioloop = None):
    self._fcn = fcn
    self._pending = None
    self._next_wakeup = 0
    self._ioloop = IOLoop.instance() if ioloop is None else ioloop
    if class_name is not None:
      self._myname = '%s.%s.guarded' %(class_name, fcn.__name__)
    else:
      self._myname = 'unknown.%s.guarded' % (fcn.__name__) 
    ExpDH.instance().WatchVariable(self, '_pending', self._myname)
    ExpDH.instance().WatchVariable(self, '_next_wakeup', self._myname)
    
  def WakeupAtNSeconds(self, wakeup_time):
    if self._pending:
      raise CallbackPendingException
    else:
      logging.info('%s going to sleep, waking up at %s', self._myname, 
                   time.ctime(wakeup_time))
      self._next_wakeup = time.ctime(wakeup_time)
      self._pending = self._ioloop.add_timeout(wakeup_time, self._WokeUp)
  
  def Remove(self):
    if self._pending:
      logging.info('removed callback for %s,%s', self._next_wakeup, self._myname)
      self._ioloop.remove_timeout(self._pending)
      self._pending = None
      self._next_wakeup = ''
      self._wakeup_time = 0
      return True
    else: 
      return False
  def _WokeUp(self):
    self._pending = False
    logging.info('%s woke up at %s', self._myname, time.ctime())
    self._fcn()
    
  def WakeupInNSeconds(self, wakeup_delta):
    return self.WakeupAtNSeconds(time.time() + wakeup_delta)
  
def GetDescendants(ins, basecls):
  #@memoize 
  def get_all_bases(cls):
    bases = []
    for b in cls.__bases__:
      bases.append(b)
      bases += get_all_bases(b)
    return bases
  rval = []
  for entity_name in dir(ins):
    thing = getattr(ins, entity_name)
    if inspect.isclass(thing):
      allbases = get_all_bases(thing)
      if basecls in allbases:
        rval.append(thing)
  return rval    

class Ting3HTTPServer(object):
  ''' This is a simple class you should inherit from. It listens on a port, and 
  implements some basic montitoring handlers. By default, you get rudimentary 
  /varz /statusz, and /quitquitquit handlers. It uses a tornado server, and 
  it's possible to start this in a thread (though it might be a bit buggy).
  
  Exported Data are automatically set up and handled by default. Any classes 
  derived from tornado.web.RequestHandler are recognized and bound automatically 
  as well.  
  
  To use:
  
  class MyHTTPServer(Ting3HTTPServer):
    NAME='My Server Name'
  
    class StopHandler(tornado.web.RequestHandler):
      URL='/stop'
      def get(self):
        StopSomething();
        self.write("Stopped")
    class RootZHandler(tornado.web.RequestHandler):
      URL='/'
      def get(self):
        self.write('my own homepage')
   ...
   ...
   
   
  Then:
  
  http = ManagerHTTPServer(port=self._port_exp)
  http.StartInIOLoop(IOLoop.instance(), startthread=False)
        
  '''

  NAME = 'Default Ting3 server'
  class VarZHandler(tornado.web.RequestHandler):
    URL = r'/varz'
    def get(self):
      logging.error('STARTING')
      self.set_header('Content-Type', 'text/plain')
      self.write(ExpDH.instance().AsText())

  class StatusZHandler(tornado.web.RequestHandler):
    URL = r'/statusz'
    def get(self):
      self.set_header('content-type', 'text/html')
      self.write(ExpDH.instance().AsHTML())

  class QuitHandler(tornado.web.RequestHandler):
    URL = r'/quitquitquit'
    def get(self):
      self.set_header('content-type', 'text/html')
      logging.error('Got Quit from user. dying')
      self.write("Dying in 5 seconds")
      iol = tornado.ioloop.IOLoop.instance()
      #iol.add_timeout(time.time() + 5, iol.stop)
      iol.add_timeout(functools.partial(sys.exit, 0), iol.stop)
  
  class RootHandler(StatusZHandler):
    URL = r'/'
  
  def __init__(self, port, name=None):
    self._name = name
    ExpDH.instance().setTitle(self.NAME if name is None else name)
    ExpDH.instance().AutoAddClass(self)
    assert port
    self._port_exp = port
    self._application = tornado.web.Application()
    self.AttachDefaultHandlers()
    
    
  def AttachDefaultHandlers(self):
    handler_list = [(h.URL, h) for h in 
                    GetDescendants(self, tornado.web.RequestHandler)]
    logging.info('got handler list %s', str(handler_list))
    self._application.add_handlers('.*$', handler_list)
    
  def StartInIOLoop(self, ioloop=None, startthread=False):
    self._ioloop = IOLoop.instance() if ioloop is None else ioloop
    self._server = httpserver.HTTPServer(self._application)
    self._server.listen(self._port_exp)
    logging.info('%s listening on %s', self._name, self._port_exp)
    if startthread:
      #TODO: figure out how to handle excepetions in this thread
      logging.info('starting new listening thread')
      self._thread = thread.start_new_thread(self._ioloop.start, tuple([]))
     

